<?php
/**
 * LogManager
 *
 * @category	Log
 * @package		dayscript.log
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */

namespace dayscript\log;

/**
 * LogManager
 *
 * Singleton
 *
 * @category	Log
 * @package		dayscript.log
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\dayscript\Enviroment, \dayscript\log\Logger, \dayscript\Map
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-09-01 $
 */
use dayscript\ErrorManager;

class LogManager	{
	/**
	 * LogManager instance for __destruct use
	 * @var LogManager $instance
	 */
	private static $instance = null;
	/**
	 * Log PHP errors. You can determine what php's information gets logged using global php constants like ( E_ALL | E_STRICT ) or 0 (cero) for no Logging.
	 * @var int $phpThreshold
	 */
	private static $phpThreshold = -1;
	/**#@+
	 * Defines the value of the type of information that will be logging.
	 * @var int
	 */
	const LOG_NONE			= 0;
	const LOG_WARNING		= 1;
	const LOG_NOTICE		= 2;
	const LOG_ERROR			= 4;
	const LOG_USER_INFO		= 8;
	const LOG_DATASOURCE	= 16;
	const LOG_ACTION		= 32;
	const LOG_BECHMARK		= 64;
	/**#@-*/
	/**
	 * Log APP errors. You can determine what application's/framework's information gets logged or 0 (cero) for no Logging.
	 * b'0000000' = No logging
	 * b'0000001' = Warnings, Deprecation
	 * b'0000010' = Notices, Information
	 * b'0000100' = Error, Exception
	 * b'0001000' = Especial user defined Information
	 * b'0010000' = Datasource requests
	 * b'0100000' = Object's actions
	 * b'1000000' = Bechmark report
	 * @var int $appThreshold
	 */
	private static $appThreshold = -1;
	/**#@+
	 * Defines the value of the type of logging.
	 * @var int
	 */
	const TYPE_NONE		= 0;
	const TYPE_DIRECT	= 1;
	const TYPE_STORED	= 2;
	/**#@-*/
	/**
	 * Log's type. You can determine where the information gets logged.
	 * b'0000000' = No Log type.
	 * b'0000001' = Log info in the exact moment that it comes.
	 * b'0000010' = Store the log info right before the script execution ends.
	 * @var int $type
	 */
	private static $type = self::TYPE_NONE;
	/**
	 * Logs Date Format, You can use PHP date codes to set your own date formatting.
	 * @var string $dateFormat
	 */
	private static $dateFormat = 'Y-m-d H:i:s.u';
	/**
	 * Logs Directory.
	 * @var string $path
	 */
	private static $path = '/tmp';
	/**
	 * Loggers list
	 * @var \dayscript\Map $loggers
	 * @access protected
	 */
	private static $loggers = null;
	/**
	 * Logs list
	 * @var array $logs[] = array( 'date' => , 'sys' => , 'log' => , 'th' => )
	 * @access protected
	 */
	private static $logs = array( );
	/**
	 * LogManager constructor
	 * @return LogManager
	 */
	private function __construct( )	{
		
	}
	/**
	 * Returns an static intance of LogManager
	 * @return LogManager
	 */
	public static function init( )	{
		if( self::$loggers == null )	{
			self::$instance = new self( );
			self::$loggers = new \dayscript\Map( );
			
			\dayscript\Enviroment::getInstance( );
			$conf = \dayscript\Enviroment::config( 'LOG', new \dayscript\Map( ) );
			
			$val = $conf->get( 'APP_THRESHOLD', self::$appThreshold );
			if( \is_string( $val ) )
				$val = \bindec( $val );
			self::$appThreshold = $val;
			
			$val = $conf->get( 'PHP_THRESHOLD', self::$phpThreshold );
			if( \is_string( $val ) )
				$val = \bindec( $val );
			self::$phpThreshold = $val;
			
			$val = $conf->get( 'TYPE', self::$type );
			if( \is_string( $val ) )
				$val = \bindec( $val );
			self::$type = $val;
			
			self::$path = $conf->get( 'PATH', self::$path );
			self::$dateFormat = $conf->get( 'DATE', self::$dateFormat );
			
			$loggers = \dayscript\Enviroment::config( 'LOGGERS', new \dayscript\Map( ) );
			foreach( $loggers as $name => $lg )	{
				if( $lg['logger'] )	{
					$className = $lg['logger'];
					try	{
						$logger = new $className( $name, $lg );
						self::$loggers[$name] = $logger;
					}
					catch( \Exception $e )	{
						//TODO
						//\error_log( \sprintf( "%s %s", ( $log['sys'] ? 'PHP' : 'APP' ), $log['log'] ), 0 );
						\dayscript\ErrorManager::exceptionHandler( $e );
					}
				}
			}
		}
	}
	/**
	 * Registers its shutdown function
	 */
	public static function registerShutdown( )	{
		\register_shutdown_function( array( __CLASS__, 'shutdownHandler' ) );
	}
	/**
	 * Whether or not an Application's log type can be logged
	 * @param binary $logType Error Type to test for logging permission.
	 * @return boolean
	 */
	public static function canLog( $logType )	{
		return ( ( self::$appThreshold & $logType ) != 0 );
	}
	/**
	 * Returns actual logType
	 * @return int
	 */
	public static function getType( )	{
		return self::$type;
	}
	/**
	 * Add a named logger.
	 * @param \dayscript\log\Logger $logger
	 */
	public static function addLogger( \dayscript\log\Logger $logger )	{
		self::$loggers[$logger->getName( )] = $logger;
	}
	/**
	 * Removes a named logger.
	 * @param string|\dayscript\log\Logger $logger
	 */
	public static function removeLogger( $logger )	{
		unset( self::$loggers[( $logger instanceOf \dayscript\log\Logger ? $logger->getName( ) : $logger )] );
	}
	/**
	 * Method to find a named logger.
	 * @param string $name
	 * @return \dayscript\log\Logger|null
	 */
	public static function getLogger( $name )	{
		return self::$loggers[$name];
	}
	/**
	 * Get an array of known logger names.
	 * @return array $keys
	 */
	public static function getLoggerNames( )	{
		return self::$loggers->keys( );
	}
	/**
	 * Adds a log entry.
	 * @param string $string Log text
	 * @param int $threshold Log Threshold
	 * @param boolean $system Whether or not is a system log
	 * @param int|null $filterThreshold Filter threshold or null for error_reporting( ) use.
	 */
	public static function log( $string, $threshold = 0, $system = false, $filterThreshold = null )	{
		self::internalLog( $string, $threshold, (bool)$system, $filterThreshold );
	}
	/**
	 * Adds a PHP log entry.
	 * @param string $string Log text
	 * @param int $logThreshold PHP Log Threshold
	 * @param int|null $filterThreshold Filter threshold or null for error_reporting( ) use.
	 */
	public static function systemLog( $string, $logThreshold = \E_UNKNOWN, $filterThreshold = null )	{
		self::internalLog( $string, $logThreshold, true, $filterThreshold );
	}
	/**
	 * Adds a application log entry.
	 * @param string $string Log text
	 * @param int $appThreshold APP Log Threshold
	 * @param int|null $filterThreshold Filter threshold or null for error_reporting( ) use.
	 */
	public static function appLog( $string, $appThreshold = self::LOG_NONE, $filterThreshold = null )	{
		self::internalLog( $string, $appThreshold, false, $filterThreshold );
	}
	/**
	 * Adds a log entry.
	 * @param string $string Log text
	 * @param int $threshold Log Threshold
	 * @param boolean $system Whether or not is a system log
	 * @param int|null $filterThreshold Filter threshold or null for error_reporting( ) use.
	 */
	private static function internalLog( $string, $threshold, $system, $filterThreshold = null )	{
		if( \is_string( $threshold ) )
			$threshold = \bindec( $threshold );
		if( \is_string( $filterThreshold ) )
			$filterThreshold = \bindec( $filterThreshold );
		$actualThreshold = ( $system === true ? self::$phpThreshold : self::$appThreshold );
		if( $threshold & $actualThreshold || $threshold === 0 )	{
			$time = \microtime( true );
			$micro = \sprintf( "%06d",( $time - \floor( $time ) ) * 1000000 );
			$dateTime = new \DateTime( \date( 'Y-m-d H:i:s.' . $micro, $time ) );
			self::$logs[] = array( 'date' => $dateTime->format( self::$dateFormat ), 'sys' => (bool)$system, 'log' => $string, 'th' => $threshold, 'fth' => $filterThreshold );
			if( ( self::$type & self::TYPE_DIRECT ) > 0 )
				self::flush( true );
		}
	}
	/**
	 * Calls every each Log object for storing log info.
	 * If there is no loggers the log will be send to the PHP log file (php.ini), but only when $silent param
	 * is set to false commonly when the scripts ends.
	 * @param boolean $silent
	 */
	public static function flush ( $silent = false )	{
		\flush( );
		if( self::$loggers && !self::$loggers->isEmpty( ) && !empty( self::$logs ) )	{
			foreach( self::$loggers as $logger )	{
				foreach( self::$logs as $log )
					$logger->store( $log['date'], $log['log'], $log['th'], $log['sys'], $log['fth'] );
				
				$logger->flush( );
			}
			self::$logs = array( );
			\flush( );
		}
		else if( !$silent )	{
			foreach( self::$logs as $log )
				\error_log( \sprintf( "%s %s", ( $log['sys'] ? 'PHP' : 'APP' ), $log['log'] ), 0 );
				//echo( \sprintf( "%s %s", ( $log['sys'] ? 'PHP' : 'APP' ), $log['log'] ) );
			self::$logs = array( );
			\flush( );
		}
	}
	/**
	 * __destruct
	 */
	public function __destruct( )	{
		\dayscript\log\LogManager::log( 'LogManager END.', \dayscript\log\LogManager::LOG_USER_INFO );
		if( self::$type & self::TYPE_STORED )
			self::$type = ( self::$type ^ self::TYPE_STORED ) | self::TYPE_DIRECT;
		self::flush( );
		self::$loggers = null;
		\flush( );
	}
	/**
	 * Global script shutdown Handler.
	 * This will send to log every pending log message and will transform the log type into a DIRECT log only when
	 * type is not NONE
	 * @param mixed $params
	 */
	public static function shutdownHandler( $params = null )	{
		\flush( );
		\dayscript\log\LogManager::log( 'SHUTDOWN LogManager (3).', \dayscript\log\LogManager::LOG_USER_INFO );
	}

}